package com.laolei.core.rest;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.laolei.core.consts.Const;
import com.laolei.core.rest.config.Host;
import com.laolei.core.rest.config.HostConfig;
import com.laolei.core.utils.JSONUtils;
import com.laolei.core.utils.ListUtils;
import com.laolei.core.utils.MapUtils;
import com.laolei.core.utils.StringUtils;

@Component
public class HttpRequestHandle {
	private final static Logger LOGGER = LoggerFactory.getLogger(HttpRequestHandle.class);

	static RestTemplate restTemplate = new RestTemplate();

	@Autowired(required = false)
	void set(RestTemplate restTemplate) {
		HttpRequestHandle.restTemplate = restTemplate;
	}

	private final static String toString(Object obj) {
		if (obj == null)
			return null;
		if (obj instanceof Number || obj instanceof Boolean)
			return String.valueOf(obj);
		if (obj instanceof String)
			return (String) obj;
		return JSONUtils.toJSONString(obj);
	}

	private final static String toArrayString(RestParam param, Object[] args) {
		List<Short> values = param.getValues();
		if (values.isEmpty())
			return param.getDefaultValue();
		if (values.size() == 1)
			return toString(args[values.get(0)]);
		String ret = "";
		for (Short id : values) {
			String str = toString(args[id]);
			if (str != null)
				ret += "," + str;
		}
		if ("".equals(ret))
			return param.getDefaultValue();
		return ret.substring(1);
	}

	private final static Object toJsonObject(RestParam param, Object[] args) {
		List<Short> values = param.getValues();
		if (values.isEmpty())
			return param.getDefaultValue();
		if (values.size() == 1)
			return args[values.get(0)];
		Object[] objs = new Object[values.size()];
		for (int i = 0; i < objs.length; i++) {
			objs[i] = args[values.get(i)];
		}
		return objs;
	}

	private final static String encodeArrayString(RestParam param, Object[] args) {
		try {
			return URLEncoder.encode(toArrayString(param, args), Const.DEFAULT_CHARSET.name());
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}

	private final static Pattern urlPathVariablePattern = Pattern.compile("\\{\\{([\\w _]*)\\}\\}+");// 解析所有{{。。。}}标识的资源
	private final static Map<String, Map<String, String>> cacheKeys = new LinkedHashMap<>();

	private final static Map<String, String> getKeys(String urlpath) {
		if (cacheKeys.containsKey(urlpath))
			return cacheKeys.get(urlpath);
		Matcher matcher = urlPathVariablePattern.matcher(urlpath);
		Map<String, String> keys = new LinkedHashMap<>();
		while (matcher.find()) {
			String key = matcher.group();
			keys.put(key, key.substring(2, key.length() - 2).trim());
		}
		cacheKeys.put(urlpath, keys);
		return keys;
	}

	private final static StringBuffer generateHost(String serverid) {
		Host host = HostConfig.getById(serverid);
		StringBuffer uri = new StringBuffer();
		if (host == null)
			return uri.append("http").append("://").append(serverid);
		uri.append(host.getScheme().name()).append("://").append(host.getHost());
		if (host.getPort() > 80) {
			uri.append(":").append(host.getPort());
		}
		return uri;
	}

	private final static String parsePathVariable(String urlpath, Collection<RestParam> paths, Object[] args) {
		Map<String, String> keys = getKeys(urlpath);
		String newurl = urlpath;
		for (Map.Entry<String, String> entry : keys.entrySet()) {
			if (paths == null || paths.isEmpty() || !paths.contains(entry.getValue())) {
				newurl = newurl.replace(entry.getKey(), "");
			} else {
				for (RestParam param : paths) {
					if (entry.getValue().equals(param.getName()))
						newurl = newurl.replace(entry.getKey(), encodeArrayString(param, args));
				}
			}
		}
		return newurl;
	}

	private final static StringBuffer parseParam(Collection<RestParam> params, Object[] args) {
		StringBuffer uri = new StringBuffer();
		if (params == null || params.size() == 0)
			return uri;
		for (RestParam param : params) {
			uri.append(param.getName());
			uri.append("=");
			uri.append(encodeArrayString(param, args));
			uri.append("&");
		}
		uri.deleteCharAt(uri.length() - 1);
		return uri;
	}

	private final static HttpHeaders parseHeader(Collection<RestParam> headers, Object[] args) {
		HttpHeaders httpHeaders = new HttpHeaders();
		if (ListUtils.isNotEmpty(headers)) {
			headers.forEach(param -> {
				String paramStr = toArrayString(param, args);
				httpHeaders.add(param.getName(), paramStr);
			});
		}
		return httpHeaders;
	}

	private final static String parseCookie(Collection<RestParam> cookies, Object[] args) {
		return ListUtils.n(cookies).list(param -> (param.getName() + "=" + encodeArrayString(param, args))).join(";");
	}

	private final static String toJsonBodyString(Collection<RestParam> bodys, Object[] args) {
		if (ListUtils.isEmpty(bodys))
			return "";
		Object obj;
		if (bodys.size() == 1) {
			obj = toJsonObject(bodys.iterator().next(), args);
		} else {
			obj = ListUtils.n(bodys).group(param -> param.getName())
					.map((name, params) -> toJsonObject(params.get(0), args)).to();
		}
		return JSONUtils.toJSONString(obj);
	}

	@SuppressWarnings("unchecked")
	private final static String toFormBodyString(Collection<RestParam> bodys, Object[] args) {
		if (ListUtils.isEmpty(bodys))
			return "";
		if (bodys.size() > 1) {
			return ListUtils.n(bodys).list(param -> param.getName() + "=" + toString(toJsonObject(param, args)))
					.join("&");
		} else {
			RestParam param = bodys.iterator().next();
			Object obj = toJsonObject(param, args);

			if (obj instanceof Number || obj instanceof Boolean)
				return String.valueOf(obj);
			if (obj instanceof String)
				return (String) obj;
			if (obj instanceof Map)
				return MapUtils.n((Map<String, Object>) obj).array((name, val) -> name + "=" + toString(val)).join("&");
			if (obj instanceof Collection)
				return ListUtils.n((Collection<Object>) obj).list(val -> toString(val)).join(",");
			if (obj.getClass().isArray())
				return ListUtils.<Object>n().a((Object[]) obj).list(val -> toString(val)).join(",");
			BeanMap map = BeanMap.create(obj);
			return MapUtils.n(map).array((name, val) -> name + "=" + toString(val)).join("&");
		}
	}

	private static URI getUri(String serverid, String urlpath, String version, Collection<RestParam> params,
			Collection<RestParam> paths, Object[] args) {

		StringBuffer uri = generateHost(serverid);
		uri.append(parsePathVariable(urlpath, paths, args));
		if (uri.indexOf("?") == -1)
			uri.append("?");
		else
			uri.append("&");
		uri.append(parseParam(params, args));
		if (StringUtils.isNotEmpty(version))
			uri.append("&version=").append(version);
		return URI.create(uri.toString());
	}

	private HttpContext send(HttpMethod method, String body, HttpHeaders header, URI url) {
		RequestEntity<String> request = new RequestEntity<>(body, header, method, url);
		try {
			ResponseEntity<String> response = restTemplate.exchange(request, String.class);
			LOGGER.debug("请求的url {} http请求响应的对象  {}", url, response.getBody());
			return new HttpContext(request, response);
		} catch (Throwable throwable) {
			LOGGER.error("发请求{}, 异常消息{}", url, throwable.getMessage());
			LOGGER.debug("发请求出现了异常", throwable);
			return new HttpContext(request, throwable);
		}
	}

	private String setBody(boolean isFrom, HttpHeaders header, Collection<RestParam> bodys, Object[] args) {
		if (isFrom) {
			header.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
			return toFormBodyString(bodys, args);
		} else {
			header.setContentType(MediaType.APPLICATION_JSON_UTF8);
			return toJsonBodyString(bodys, args);
		}
	}

	public HttpContext handle(RestClientData restData, Object[] args) {

		Map<Integer, Collection<RestParam>> variables = restData.getVariable();
		Collection<RestParam> params = variables.get(0);
		Collection<RestParam> headers = variables.get(1);
		Collection<RestParam> bodys = variables.get(2);
		Collection<RestParam> cookies = variables.get(3);
		Collection<RestParam> paths = variables.get(4);

		HttpHeaders header = parseHeader(headers, args);
		header.add(HttpHeaders.COOKIE, parseCookie(cookies, args));

		String body = setBody(restData.isFrom(), header, bodys, args);

		String serverid = restData.getServerid();
		String urlpath = restData.getPath();
		String version = restData.getVersion();
		URI url = getUri(serverid, urlpath, version, params, paths, args);

		HttpMethod method = restData.getMethod();
		return send(method, body, header, url);
	}
}
